using System;
using System.Linq;
using System.Collections.Generic;
using System.Text.Json;

namespace sd
{
    public class SdEngine
    {
        private static int index = 0;
        private static List<string> bufferData = new List<string>();

        public int Step { set; get; }

        private readonly IEnumerable<int> __MAX9_NUMBERS = Enumerable.Range(1, 9);

        public SdModel CurrentModel
        {
            set; get;
        }

        public SdEngine()
        {

        }

        public SdEngine(SdModel model)
        {
            CurrentModel = model;
        }

        public bool MoveNext()
        {
            index++;
            if (isComplete())
            {
                Console.WriteLine("操作已完成！");
                return false;
            }
            bool isComputeValue = false;
            var nullPoints = CurrentModel.Points.Where(p => p.Value == 0);

            foreach (var point in nullPoints)
            {
                int value = 0;
                var enablesNums = GetMissingNumbers(point);
                point.EnableNums = enablesNums;

                if (point.EnableNums.Count() == 0)
                {
                    //数字猜测失败
                    Console.WriteLine("数字猜测失败，正在回滚数据");
                    Restore();
                    isComputeValue = true;
                    break;
                }
                else if (point.EnableNums.Count() == 1)
                {
                    value = point.EnableNums.ElementAt(0);
                    point.Value = value;
                    point.Step = ++Step;

                    CurrentModel.PrintToConsole(point.Step);
                    isComputeValue = true;
                    break;
                }
            }
            if (!isComputeValue)
            {
                foreach (var point in nullPoints)
                {
                    //区域碾除
                    var partNullPoints =
                        CurrentModel.
                        Points.
                        Where(p => p.Value == 0 && p.Part == point.Part && (p.X != point.X || p.Y != point.Y));

                    var xNullPoints = CurrentModel.
                        Points.
                        Where(p => p.Value == 0 && p.X == point.X && p.Y != point.Y);

                    var yNullPoints = CurrentModel.
                          Points.
                          Where(p => p.Value == 0 && p.X != point.X && p.Y == point.Y);

                    foreach (int number in point.EnableNums)
                    {
                        var count = partNullPoints.Count(p => p.EnableNums.Contains(number));
                        if (count == 0)
                        {
                            point.Value = number;
                            point.Step = ++Step;

                            CurrentModel.PrintToConsole(point.Step);
                            isComputeValue = true;
                            break;
                        }

                        count = xNullPoints.Count(p => p.EnableNums.Contains(number));
                        if (count == 0)
                        {
                            point.Value = number;
                            point.Step = ++Step;

                            CurrentModel.PrintToConsole(point.Step);
                            isComputeValue = true;
                            break;
                        }

                        count = yNullPoints.Count(p => p.EnableNums.Contains(number));
                        if (count == 0)
                        {
                            point.Value = number;
                            point.Step = ++Step;

                            CurrentModel.PrintToConsole(point.Step);
                            isComputeValue = true;
                            break;
                        }
                    }

                    if (isComputeValue)
                        break;
                }
            }

            //猜
            if (!isComputeValue)
            {
                var minEnableNumsCount = nullPoints.Select(p => p.EnableNums.Count()).Min();
                var point = nullPoints.FirstOrDefault(p => p.EnableNums.Count() == minEnableNumsCount);
                if (point == null)
                {
                    Console.WriteLine("一半情况下不会出现");
                    return false;
                }

                int guessNum = point.EnableNums.First();
                point.GuessedNums.Add(guessNum);
                Save();
                Console.WriteLine("以下步骤数据可能不正确");
                point.Value = guessNum;
                point.Step = ++Step;

                CurrentModel.PrintToConsole(point.Step);
                isComputeValue = true;
            }

            if (!isComputeValue)
                Console.WriteLine("暂无找到 数字");

            return isComputeValue;
        }
        public bool isComplete()
        {
            return CurrentModel.Points.All(p => p.Value > 0);
        }

        public void Save()
        {
            string buffer = JsonSerializer.Serialize(this);
            bufferData.Add(buffer);
        }

        public void Restore()
        {
            if (bufferData.Count == 0)
                throw new Exception("没有数据可以还原");

            var buffer = bufferData[^1];
            var backup = JsonSerializer.Deserialize<SdEngine>(buffer);
            this.CurrentModel = backup.CurrentModel;
            this.Step = backup.Step;

            bufferData.RemoveAt(bufferData.Count - 1);
            Console.WriteLine("回滚成功，当前为：");
            CurrentModel.PrintToConsole();
        }

        private IEnumerable<int> GetEnableNumbers(IEnumerable<int> mumbers)
        {
            return __MAX9_NUMBERS.Except(mumbers);
        }

        private IEnumerable<int> GetMissingNumbers(SdPointModel point)
        {
            var disableNums = GetDisableNumbers(point);
            var enableNums = GetEnableNumbers(disableNums);
            return enableNums;
        }

        private IEnumerable<int> GetDisableNumbers(SdPointModel point)
        {
            var xNumbers = CurrentModel.Points.Where(p => p.X == point.X).Select(p => p.Value);
            var yNumbers = CurrentModel.Points.Where(p => p.Y == point.Y).Select(p => p.Value);
            var partNumbers = CurrentModel.Points.Where(p => p.Part == point.Part).Select(p => p.Value);

            return xNumbers.Union(yNumbers).Union(partNumbers);
        }
    }
}